package org.uu.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.codec.binary.Base64;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;
import org.uu.bean.User;
import org.uu.util.FileReadUtil;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;

import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

public class JwtTools {
    /**
     * token秘钥，请勿泄露，请勿随便修改 backups:JKKLJOoasdlfj
     */
    public static final String SECRET = "JKKLJOoasdlfj";
    /**
     * token 过期时间: 5分钟
     */
    public static final int calendarField = Calendar.SECOND;
    public static final int calendarInterval = 60 * 5;

    public JwtTools() {

    }

    /**
     * JWT生成Token.<br/>
     *
     * JWT构成: header, payload, signature
     * @param user
     * @return
     * @throws Exception
     */
    public static String createToken4HS256(User user) {
        Date iatDate = new Date();
        // expire time
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(calendarField, calendarInterval);
        Date expiresDate = nowTime.getTime();

        // header Map
        Map<String, Object> map = new HashMap<>();
        map.put("alg", "HS256");
        map.put("typ", "JWT");

        // build token
        // param backups {iss:Service, aud:APP}
        String token = JWT.create().withHeader(map) // header
                .withClaim("iss", "Service") // payload
                .withClaim("aud", "APP")
                .withClaim("user_id", user.getId())
                .withClaim("user_name", user.getName())
                .withClaim("user_role", user.getRole())
                .withIssuedAt(iatDate) // sign time
                .withExpiresAt(expiresDate) // expire time
                .sign(Algorithm.HMAC256(SECRET)); // signature

        return token;
    }


    /**
     * 解密Token
     * @param token
     * @return
     */
    public static Map<String, Claim> verifyToken4HS256(String token) {
        DecodedJWT jwt = null;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            jwt = verifier.verify(token);
        } catch (Exception e) {
          // e.printStackTrace();
          // token 校验失败, 抛出Token验证非法异常
        }
        return jwt.getClaims();
    }


    public static String createToken4RS256(User user)  {

        String s = FileReadUtil.readFile("D:\\devpdir\\debug\\java-jwt\\src\\main\\resources\\rsa256_private.key");
        PrivateKey privateKey = getPrivateKey(s);

        final JwtClaims claims = new JwtClaims();
        claims.setClaim("user_name", user.getName());
        claims.setClaim("user_id", user.getId());
        claims.setClaim("user_role", user.getRole());

        claims.setSubject("u@blake");
        claims.setAudience("blake"); // 用于验证签名是否合法 验证方法必须包含这些内容才能通过
        claims.setExpirationTimeMinutesInTheFuture(60*24*30);
        claims.setIssuedAtToNow();

        // generate the payload
        final JsonWebSignature jws = new JsonWebSignature();
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
        jws.setPayload(claims.toJson());
        jws.setKeyIdHeaderValue(UUID.randomUUID().toString());

        // sign using the private key
        jws.setKey(privateKey);

        try {
           return jws.getCompactSerialization();
        } catch (JoseException e) {
            e.printStackTrace();
            return null;
        }
    }

    private static PrivateKey getPrivateKey(String privateKey) {
        byte[] decodeBase64 = Base64.decodeBase64(privateKey);

        try {
            DerInputStream derReader = new DerInputStream(decodeBase64);
            DerValue[] seq = derReader.getSequence(0);
            if(seq.length < 9) {
                throw new GeneralSecurityException("Could not read private key");
            }

            // skip version seq[0]
            BigInteger modulus = seq[1].getBigInteger();
            BigInteger publicExp = seq[2].getBigInteger();
            BigInteger privateExp = seq[3].getBigInteger();
            BigInteger primeP = seq[4].getBigInteger();
            BigInteger primeQ = seq[5].getBigInteger();
            BigInteger expP = seq[6].getBigInteger();
            BigInteger expQ = seq[7].getBigInteger();
            BigInteger crtCoeff = seq[8].getBigInteger();

            RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, primeP, primeQ,expP,
                    expQ, crtCoeff);
            KeyFactory rsa = KeyFactory.getInstance("RSA");
            return rsa.generatePrivate(keySpec);
        } catch (IOException | GeneralSecurityException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static JwtClaims verifyToken4RS256(String token)  {
        String s = FileReadUtil.readFile("D:\\devpdir\\debug\\java-jwt\\src\\main\\resources\\rsa256_public.key");
        JwtClaims jwtClaims = null;
        try {
            PublicKey publicKey = getPublicKey(s);

            JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                    .setRequireExpirationTime()
                    .setVerificationKey(publicKey)
                    .setExpectedAudience("blake")
                    .build();

            jwtClaims = jwtConsumer.processToClaims(token);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidJwtException e) {
            e.printStackTrace();
            return null;
        }
        return jwtClaims;
    }

    private static PublicKey getPublicKey(String publicKeyBase64) throws NoSuchAlgorithmException, InvalidKeySpecException {
        java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyBase64));
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
        return publicKey;
    }

}
